﻿using System;
using System.ComponentModel;
using System.IO;

namespace Heimdallr
{
    /// <summary>
    /// Represents a notification and its properties and data. The data is buffered in a byte array.
    /// </summary>
    public class BufferedNotification : Notification
    {
        #region Field

        internal readonly static byte[] EmptyBuffer = new byte[0];

        private readonly NotificationLevel level;
        private readonly string contentType;
        private readonly int contentLength;
        private readonly DateTime time;
        private readonly TimeSpan duration;
        private readonly byte[] data;

        #endregion

        #region Property

        /// <summary>
        /// Gets the time of this notification generated.
        /// </summary>
        public sealed override DateTime Time
        {
            get { return this.time; }
        }

        /// <summary>
        /// Gets the effective duration of this notification.
        /// </summary>
        /// <remarks>
        /// Negative value means the duration is infinity.
        /// </remarks>
        public sealed override TimeSpan Duration
        {
            get { return this.duration; }
        }

        /// <summary>
        /// Gets the level of this notification.
        /// </summary>
        /// <remarks>
        /// The default value is <see cref="NotificationLevel">NotificationLevel.Info</see>.
        /// </remarks>
        public sealed override NotificationLevel Level
        {
            get { return this.level; }
        }

        /// <summary>
        /// Gets the MIME type of this notification.
        /// </summary>
        /// <remarks>The value is typically the MIME encoding of the content of this notification.</remarks>
        public sealed override string ContentType
        {
            get { return this.contentType; }
        }

        /// <summary>
        /// Gets the content length of this notification.
        /// </summary>
        public sealed override int ContentLength
        {
            get { return this.contentLength; }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the BufferedNotification class, using specified level.
        /// </summary>
        /// <param name="level">The level of this notification.</param>
        /// <exception cref="InvalidEnumArgumentException"></exception>
        public BufferedNotification(NotificationLevel level)
            : this(level, EmptyBuffer, DefaultContentType, DateTime.Now, TimeSpan.Zero)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BufferedNotification"/> class, using specified level and data.
        /// </summary>
        /// <param name="level">The level of this notification.</param>
        /// <param name="data">The data of this notification.</param>
        /// <exception cref="InvalidEnumArgumentException">Parameter <paramref name="level"/> is not a valid value.</exception>
        /// <exception cref="ArgumentNullException">Parameter <paramref name="data"/> is null.</exception>
        public BufferedNotification(NotificationLevel level, byte[] data)
            : this(level, data, DefaultContentType, DateTime.Now, TimeSpan.Zero)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BufferedNotification"/> class, using specified level, data and MIME type.
        /// </summary>
        /// <param name="level">The level of this notification.</param>
        /// <param name="data">The data of this notification.</param>
        /// <param name="contentType">The content type of this notification.</param>
        /// <exception cref="InvalidEnumArgumentException">Parameter <paramref name="level"/> is not a valid value.</exception>
        /// <exception cref="ArgumentNullException">Parameter <paramref name="data"/> or <paramref name="contentType"/> is null.</exception>
        public BufferedNotification(NotificationLevel level, byte[] data, string contentType)
            : this(level, data, contentType, DateTime.Now, TimeSpan.Zero)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BufferedNotification"/> class, using specified level and data, MIME type and generated time.
        /// </summary>
        /// <param name="level">The level of this notification.</param>
        /// <param name="data">The data of this notification.</param>
        /// <param name="contentType">The content type of this notification.</param>
        /// <param name="time">The time of this notification generated.</param>
        /// <exception cref="InvalidEnumArgumentException">Parameter <paramref name="level"/> is not a valid value.</exception>
        /// <exception cref="ArgumentNullException">Parameter <paramref name="data"/> or <paramref name="contentType"/> is null.</exception>
        public BufferedNotification(NotificationLevel level, byte[] data, string contentType, DateTime time)
            : this(level, data, contentType, time, TimeSpan.Zero)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BufferedNotification"/> class, using specified level, data, MIME type, generated time and duration.
        /// </summary>
        /// <param name="level">The level of this notification.</param>
        /// <param name="data">The data of this notification.</param>
        /// <param name="contentType">The content type of this notification.</param>
        /// <param name="time">The time of this notification generated.</param>
        /// <param name="duration">The effective duration of this notification. Negative value means the duration is infinity.</param>
        /// <exception cref="InvalidEnumArgumentException">Parameter <paramref name="level"/> is not a valid value.</exception>
        /// <exception cref="ArgumentNullException">Parameter <paramref name="data"/> or <paramref name="contentType"/> is null.</exception>
        public BufferedNotification(NotificationLevel level, byte[] data, string contentType, DateTime time, TimeSpan duration)
        {
            if (!Enum.IsDefined(typeof(NotificationLevel), level))
                throw new InvalidEnumArgumentException("level", (int)level, typeof(NotificationLevel));

            if (data == null)
                throw new ArgumentNullException("data");

            if (contentType == null)
                throw new ArgumentNullException("contentType");

            this.level = level;
            this.contentType = contentType;
            this.contentLength = data.Length;
            this.time = time;
            this.duration = duration.Duration();
            this.data = data;
        }

        #endregion

        #region Method

        /// <summary>
        /// Creates a read-only stream instance allows user to read the data of notification.
        /// </summary>
        /// <returns>A read-only stream instance.</returns>
        public sealed override Stream CreateReadOnlyStream()
        {
            if (this.ContentLength == 0)
                return Stream.Null;
            else
                return new MemoryStream(this.data, false);
        }

        /// <summary>
        /// Creates a read-only stream instance with specified buffer size allows user to read the data of notification.
        /// </summary>
        /// <param name="bufferSize">The size of the buffer. This parameter will be ignored.</param>
        /// <returns>A read-only stream instance.</returns>
        public sealed override Stream CreateReadOnlyStream(int bufferSize)
        {
            if (this.ContentLength == 0)
                return Stream.Null;
            else
                return new MemoryStream(this.data, false);
        }

        /// <summary>
        /// Writes the notification data to a new byte array and returns it.
        /// </summary>
        /// <returns>A new byte array.</returns>
        public sealed override byte[] GetBuffer()
        {
            if (this.ContentLength == 0)
                return EmptyBuffer;

            using (MemoryStream ms = new MemoryStream(this.data, false))
                return ms.ToArray();
        }

        #endregion
    }
}